package com.Jning.cfengtestdemo.websocket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Cfeng
 * @date 2022/8/2
 * 测试websocket的服务端EndPoint的使用
 * 作为websocket服务器端，为一个controller处理器，加上注解@Component
 */

@Component
@Slf4j
@ServerEndpoint("/chat/{username}")  //表明为websocket服务器端，指明访问的url地址
public class ChatEndPoint {
    //记录当前连接数量,在多线程状态下应该设置为线程安全的
    private static int onlineCount = 0;
    //线程安全的ConcurrentHashMap，存放每一个客户端对应的服务端ChatEndPoint对象，单一服务就直接使用Map，每一个用户对应一个EndPoint
    private static Map<String,ChatEndPoint> webSocketMap = new ConcurrentHashMap<>();

    //与某个服务端连接的会话,websocket下面
    private Session session;

    //区分不同的客户端的标识，这里直接就是username
    private String currentUser = "";

    //维护已经创建的服务端端点实例,使用线程安全的CopyOnWriteArraySet
//    private static Set<ChatEndPoint> chatEndPoints = new CopyOnWriteArraySet<>();

    //维护在线的用户列表, 与set合并就是上面的map
//    private static HashMap<String,String> users = new HashMap<>();

    /**
     * 该方法处理某个客户端连接成功后指向的方法，Session为连接的会话，可以通过其发送数据给客户端
     * 连接时会接收severEndpoint的username进行处理
     * 连接会传入一个session
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        this.session = session; //会话赋值供其他的方法使用，当前用户的session
        this.currentUser = username;
        webSocketMap.put(currentUser,this); //增加在线的用户，key就是用户访问，value为其对应的EndPoint
        addOnlineCount();
        log.info("有新人加入，当前在线" + getOnlineCount());
        log.info("session = {} ||| username ={}",session,username);
        //广播该用户上线消息
        broadCast(username + "上线了");
    }

    /**
     * 与该客户端连接关闭后调用的方法
     */
    @OnClose
    public void onClose() {
        if(!currentUser.equals("")) {
            webSocketMap.remove(currentUser); //下线移除
            subOnlineCount(); //在线人数减少
            log.info("有用户下线，当前在线" + getOnlineCount());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     * message 就是客户EndPoint但发送过来的消息
     * 都是平等的
     */
    @OnMessage
    public void onMessage(String message ,Session session) {
        log.info("来自客户端消息" + message);
    }

    /**
     * 连接错误时指向的方法
     * error就是发生的错误
     */
    @OnError
    public void OnError(Session session, Throwable error) {
        log.error("发生错误" + error.toString());
    }

    /**
     * 发送消息给用户，这里就是默认当前的endpoint
     * 使用连接的Session
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 获取系统当前Time
     */
    private  String getNowTime() {
        Date date = new Date();
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return format.format(date);
    }

    /**
     * 给指定的用户发送消息，调用上面的方法即可
     */
    public void sendToUser(String username, String message) {
        String nowTime = getNowTime();

        //需要用户在线才可以发送,socketMap就是用户连接就会生成一个EndPoint对象
        try {
            if(webSocketMap.get(username) != null) {
                webSocketMap.get(username).sendMessage(nowTime + "用户" + username + ":消息" + message);
            } else {
                log.error("当前用户不在线");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 广播消息
     * 当前用户也是一个EndPoint，需要排除
     */
    public void  broadCast(String message) {
        log.info("broadCast(),message={}",message);
        webSocketMap.keySet().forEach(key -> {
            if(!Objects.equals(currentUser,key)) {
                ChatEndPoint endPoint = webSocketMap.get(key); //代表的用户对应的端点
                synchronized (endPoint) { //必须加锁，保证线程安全
                    try {
                        endPoint.sendMessage(message); //向该哦那个胡发送消息
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }


    /**
     * 改变当前在线人数，需要注意方法应该线程安全
     */
    public static  synchronized  int getOnlineCount() {
        return onlineCount;
    }

    /**
     * 可修改的成员变量一次只能一个线程修改
     */
    public static synchronized void addOnlineCount() {
        ChatEndPoint.onlineCount ++;
    }

    public static synchronized void subOnlineCount() {
        ChatEndPoint.onlineCount --;
    }
}

